Skip to content

[v1.4] 脚本资源更新逻辑重构 ( updateResource 相关代码 ) 与 并发控制优化 (资源更新同步机制) 重构#1193

Merged
CodFrm merged 56 commits into
scriptscat:release/v1.4from
cyfung1031:pr-updateResourceByType-return
Jun 11, 2026
Merged

[v1.4] 脚本资源更新逻辑重构 ( updateResource 相关代码 ) 与 并发控制优化 (资源更新同步机制) 重构#1193
CodFrm merged 56 commits into
scriptscat:release/v1.4from
cyfung1031:pr-updateResourceByType-return

Conversation

@cyfung1031

@cyfung1031 cyfung1031 commented Feb 3, 2026

Copy link
Copy Markdown
Collaborator

1. 变更概述 (Overview)

本 PR 对脚本资源的更新逻辑进行了深度重构,核心目标是提升在复杂场景下资源更新的稳定性效率
通过引入并发控制机制统一资源获取接口并修复多项历史遗留的逻辑问题,确保了系统在大规模资源更新及高频请求环境下的可靠性,有效预防了 Service Worker 可能出现的崩溃或请求堆积问题。


2. 主要改动 (Key Changes)

核心逻辑重构

  • 接口升级:将 updateResourceByType 扩展为 updateResourceByTypes,实现了多类型资源的并行处理能力。
  • 函数语义化:对核心函数进行了重命名与精简(如 loadByUrl $\rightarrow$ createResourceByUrlFetch),使代码意图更明确。
  • 路径处理:优化了针对本地资源(file:///)的路径识别与更新逻辑,增强了兼容性。

并发控制与性能优化

  • 新增工具类:引入 concurrency-control.ts 模块,为资源下载(fetch)过程增加并行上限限制。
  • 资源节流:有效缓解了资源密集型脚本在更新时引发的网络拥堵,提升了整体响应速度。

系统健壮性提升 (Service Worker)

  • 同步机制修复:修正了 runtime.tsscript.ts 中关于资源状态同步的 BUG,确保前后端数据一致。
  • 流程精简:优化 synchronize.ts 逻辑,剔除冗余的数据库读写操作,降低 I/O 开销。

工程质量与测试

  • 代码规范:统一函数签名,移除过时或冗余参数,提升代码库的可维护性。
  • 测试覆盖:新增并同步更新了 resourceutilsconcurrency-control 的单元测试,确保重构逻辑的零退化。

3. 技术细节 (Technical Details)

维度 实现方案
并发策略 基于 concurrency-control 模块,动态限制资源下载的最大并发数,平衡更新速度与系统负载。
数据访问 通过 getScriptResourceValue 标准化接口,统一了资源值的读取路径。
缺陷修复 彻底解决了特定条件下并行 fetch 冲突导致更新任务挂起的问题。

我都不知道要怎么说明了

反正就是有问题的部份改了

有些觉得奇怪的,我没有改,只加了 comment

  • 主要移除了 loadNow 那个设定。代码太难维护了。所有 fetch 资源都加一个 concurrency 控制,避免一次过爆发就好。要不要等就上层决定吧。
  • 现在 concurrency 控制 是五个 fetch 一组。这样就不会大量 fetch 冲到同一个 server (例如 GreasyFork )

变更内容 Changes

截图 Screenshots

@cyfung1031 cyfung1031 requested a review from CodFrm February 3, 2026 10:04
@cyfung1031 cyfung1031 marked this pull request as draft February 3, 2026 10:09
@cyfung1031

cyfung1031 commented Feb 3, 2026

Copy link
Copy Markdown
Collaborator Author

弹出安装页的部份要先下载好资源
而不是到按安裝才下载
所以先改成Draft PR

UI 部份另行处理
先处理好 JS代码的改动

Comment on lines 409 to 431
await this.scriptCodeDAO.save({
uuid: script.uuid,
code: param.code,
});
logger.info("install success");

// Cache更新 & 下载资源
await Promise.all([
compiledResourceUpdatePromise,
this.resourceService.updateResourceByType(script, "require"),
this.resourceService.updateResourceByType(script, "require-css"),
this.resourceService.updateResourceByType(script, "resource"),
this.resourceService.updateResourceByTypes(script, ["require", "require-css", "resource"]),
]);
// 如果资源不完整,还是要接受安装吗???

// 广播一下
// Runtime 會負責更新 CompiledResource
this.publishInstallScript(script, { update, upsertBy });

return { update };
})
.catch((e: any) => {
logger.error("install error", Logger.E(e));
throw e;
});

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CodFrm scriptDAO 先 save 了显示成功。 如果资源下载失败,忽略掉是故意的吗?

是的话我就不改这做法
否则就改成下载不了不安装不更新

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

故意的,资源失败不影响脚本保存。不过这块逻辑可能还得优化一下

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

那我这个 PR 先不处理这个,先维持

Comment on lines +192 to +195
const updateTime = oldResources?.updatetime;
// 资源最后更新是24小时内则不更新
// 这里是假设 resources 都是 static. 使用者应该加 ?d=xxxx 之类的方式提示SC要更新资源
if (updateTime && updateTime > Date.now() - 86400_000) return;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CodFrm 原代码有这个 updateTime 检查操作。但脚本更新可以是每6小时做一次。如果脚本更新了但资源不更新,好像有点奇怪

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

之前考虑过不做这个检查,感觉也可以,资源加载下来就不再失效,除非修改url

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我先不改动。你有需要再改一下吧

@cyfung1031 cyfung1031 marked this pull request as ready for review April 11, 2026 04:32
@cyfung1031

Copy link
Copy Markdown
Collaborator Author

@CodFrm 修改好了。你可以给AI看

@cyfung1031 cyfung1031 changed the title [v1.4] updateResource 相关代码调整 [v1.4] 脚本资源更新逻辑重构 ( updateResource 相关代码 ) 与 并发控制优化 (资源更新同步机制) 重构 Apr 23, 2026
@cyfung1031

cyfung1031 commented Apr 23, 2026

Copy link
Copy Markdown
Collaborator Author

@CodFrm 这个在 1.4 版加入吧。不然又要拖多一两个月了。
内容有需要改的话就改吧

@CodFrm

CodFrm commented Apr 23, 2026

Copy link
Copy Markdown
Member

@CodFrm 这个在 1.4 版加入吧。不然又要拖多一两个月了。 内容有需要改的话就改吧

明白,我尽快,看看有没有精力来处理

@CodFrm

CodFrm commented Jun 10, 2026

Copy link
Copy Markdown
Member

本地看了一轮,结论:暂不建议合并。这次 PR 的资源下载链路能跑到,但有两个确定问题会影响核心目标。

阻塞问题

1. Semaphore 不能可靠限制并发上限

位置:src/pkg/utils/concurrency-control.ts

当前实现里 release()active--,再 queue.shift()?.() 唤醒等待者。被唤醒的等待者要等后续 microtask 才继续执行 active++,这中间如果有新请求调用 acquire(),会看到 active < limit 并直接进入,随后原等待者也进入,导致实际并发超过 limit。

我用当前实现做了最小复现,limit=1 时最大同时进入临界区达到了 3:

enter first active 1 sem.active 1
enter newcomer active 2 sem.active 2
enter queued active 3 sem.active 2
max 3 sem.active 2

这会让“最多 5 个 fetch”的核心承诺不成立。建议释放时把槽位直接转交给队列里的 waiter,或者让 waiter 被唤醒时不要再无条件 active++;同时补一个覆盖“release 后立即有 newcomer acquire”的单测。

2. 新下载成功的资源没有写 updatetime,24 小时缓存判断会失效

位置:src/app/service/service_worker/resource.ts

updateResourceByTypes() 依赖 oldResources.updatetime 判断 24 小时内不更新:

if (updateTime && updateTime > Date.now() - RESOURCE_CACHE_TTL_MS) return;

createResourceByUrlFetch() 创建成功资源时只写了 createtime: Date.now(),没有写 updatetime。首次下载成功保存后,后续 updateResourceByTypes() 看到 contentType 存在但 updatetime 为空,会继续重新 fetch,PR 描述里的“资源缓存 24 小时 / 避免重复请求”不会生效。

老代码 updateResource() 会给重新加载的 resource 设置 updatetime = now,这里属于行为回退。建议创建 resource 时同步写 createtimeupdatetime,并补测试覆盖“新资源保存后 24 小时内 updateResourceByTypes 不再 fetch”。

本地验证结果

已通过:

pnpm exec vitest --test-timeout=500 --no-coverage --isolate=false --reporter=verbose --run \
  src/app/service/service_worker/resource.test.ts \
  src/app/service/service_worker/utils.test.ts \
  src/pkg/utils/concurrency-control.test.ts

结果:3 passed / 31 passed

已通过:

pnpm run typecheck

已通过构建:

pnpm run dev

结果:Rspack compiled with 2 warnings,warning 是现有的 CSS order / monaco dynamic require。

一次性 e2e 验证

我按 docs/VERIFICATION.md 写了 e2e/scratch/verify-pr1193-resource.spec.ts,跑完已删除,未留下工作树改动。

验证内容:

  • 启动真实 dist/ext 扩展;
  • 两阶段开启 Chrome userScriptsAccess
  • 本地 HTTP server 提供 .user.js
  • 脚本包含 8 个 @require 和 1 个 @resource
  • 安装页真实点击安装;
  • 打开目标页等待用户脚本输出资源读取结果。

观察结果:资源下载链路确实跑到了,9 个资源全部被请求:

[pr1193] open install page
[pr1193] install clicked
[pr1193] logs:
2026-06-10 19:32:57 [debug] scripting start {"env":"scripting"}
2026-06-10 19:32:57 [debug] content start {"env":"content","href":"http://example.com/pr1193"}
2026-06-10 19:32:57 [debug] inject start {"env":"inject","href":"http://example.com/pr1193"}
[pr1193] resourceRequests:
/require-7.js,/require-1.js,/require-6.js,/require-2.js,/require-4.js,/require-5.js,/data.txt,/require-0.js,/require-3.js

但目标页没有等到用户脚本自己的 [pr1193] passed=true 日志,所以这个 e2e 不能作为完整通过证据。失败点更像安装后注册/注入执行链路还需要继续排查;不过上面两个代码问题已经足够阻止合并。

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Comment on lines +315 to +327
// 滑动窗口语义:
// - fetch 超时 (timeouted=true) → 提前归还槽位,下一个请求可以启动
// - fetch 完成/失败 (settled=true) → 归还槽位(若 timeout 已归还则为 no-op)
// 原 fetch 在超时后仍继续运行,响应到达时照常处理(不会被取消)
const { result, err } = await withTimeoutNotify(
fetch(url),
FETCH_SLOT_SLIDE_TIMEOUT_MS,
({ settled, timeouted }) => {
if (timeouted || settled) {
releaseSlotOnce();
}
}
);
Comment on lines +167 to 172
if (u.url && !u.url.startsWith("file:///") && oldResources?.contentType) {
const updateTime = oldResources.updatetime;
// 资源最后更新是24小时内则不更新
// 这里是假设 resources 都是 static. 使用者应该加 ?d=xxxx 之类的方式提示SC要更新资源
if (updateTime && updateTime > Date.now() - RESOURCE_CACHE_TTL_MS) return;
}
Comment on lines +172 to +176
it.concurrent("超时后回调被调用,promise 完成后再次调用", async () => {
vi.useFakeTimers();
let resolvePromise: (v: string) => void;
const promise = new Promise<string>((r) => {
resolvePromise = r;
Comment on lines +202 to +206
it.concurrent("超时后 promise 失败,回调也被调用两次", async () => {
vi.useFakeTimers();
let rejectPromise: (e: Error) => void;
const promise = new Promise<string>((_, reject) => {
rejectPromise = reject;
Comment on lines +129 to +137
it.concurrent("double release 输出警告", () => {
const sem = new Semaphore(1);
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});

sem.release();
expect(warnSpy).toHaveBeenCalledWith("Semaphore double release detected");

warnSpy.mockRestore();
});
CodFrm added 2 commits June 11, 2026 14:02
updateResourceByTypes 在 24 小时 TTL 命中时直接 return,不会把当前脚本 uuid 写入 resource.link。多个脚本复用同一 URL 时,删除其中一个脚本会把仍被其它脚本使用的资源一并删除。

TTL 命中时也补登记 link(只更新 link 字段,避开 getResourceModel 对 SRI 校验失败时改写 content 的副作用)。
- gm-api.spec.ts: scriptscat#1477 迁移到本地 mock server 时漏改 unwrap 测试,仍引用已删除的 TARGET_URL 导致 ReferenceError。改用 cspOrigin,并把 unwrap_e2e_test token 接入 patchTargetMatchCode

- concurrency-control.test.ts: 操作全局计时器/console 的 3 个用例从 it.concurrent 改回顺序 it,避免并发互相干扰偶现失败
@CodFrm CodFrm merged commit 9076794 into scriptscat:release/v1.4 Jun 11, 2026
4 checks passed
CodFrm added a commit that referenced this pull request Jun 12, 2026
为 v1.4.0-beta.3 → beta.4 的核心改动补充 Playwright E2E(复用现有两阶段
userScripts fixture,并新增本地 mock server + host-resolver 域名映射,全程不依赖外网):

- resource-update: @require/@resource 注入与读取、24h TTL 同 URL 去重、
  拉取失败优雅降级、多 require 并发全部加载 (#1193)
- gm-xhr-site-access: 跨域 @connect 放行、x-sc-request-marker 经 DNR 剥离、
  非 @connect 域名拒绝 (#1477, mv3_utils)
- background-script: @background 脚本经 Chrome offscreen 执行并具备 DOM 环境 (#1457)
- user-config-yaml: 含 __proto__ 的恶意 @userconfig 被拒、原型链未污染 (#1494)
- backup-zip: 备份导出/导入 zip 往返(JSZipp generateAsync/loadAsync)(#1479)
- server-fixtures: 共享 fixture + mock server
CodFrm added a commit that referenced this pull request Jun 12, 2026
为 v1.4.0-beta.3 → beta.4 的核心改动补充 Playwright E2E(复用现有两阶段 userScripts fixture,并新增本地 mock server + host-resolver 域名映射,全程不依赖外网):

- resource-update: @require/@resource 注入与读取、24h TTL 同 URL 去重、拉取失败优雅降级、多 require 并发全部加载 (#1193)

- gm-xhr-site-access: 跨域 @connect 放行、x-sc-request-marker 经 DNR 剥离、非 @connect 域名拒绝 (#1477, mv3_utils)

- background-script: @background 脚本经 Chrome offscreen 执行,具备 DOM 环境,并通过脚本内 GM 值读写闭环 + mock server probe 做稳定断言 (#1457)

- user-config-yaml: 含 __proto__ 的恶意 @userconfig 被拒、原型链未污染 (#1494)

- backup-zip: 备份导出/导入 zip 往返(JSZipp generateAsync/loadAsync)(#1479)

- e2e waits: 移除固定 sleep/忙等,改为条件等待;并升级 workflow action runtime 版本

- server-fixtures: 共享 fixture + mock server
CodFrm added a commit that referenced this pull request Jun 12, 2026
为 v1.4.0-beta.3 → beta.4 的核心改动补充 Playwright E2E(复用现有两阶段 userScripts fixture,并新增本地 mock server + host-resolver 域名映射,全程不依赖外网):

- resource-update: @require/@resource 注入与读取、24h TTL 同 URL 去重、拉取失败优雅降级、多 require 并发全部加载 (#1193)

- gm-xhr-site-access: 跨域 @connect 放行、x-sc-request-marker 经 DNR 剥离、非 @connect 域名拒绝 (#1477, mv3_utils)

- background-script: @background 脚本经 Chrome offscreen 执行,具备 DOM 环境,并通过脚本内 GM 值读写闭环 + mock server probe 做稳定断言 (#1457)

- user-config-yaml: 含 __proto__ 的恶意 @userconfig 被拒、原型链未污染 (#1494)

- backup-zip: 备份导出/导入 zip 往返(JSZipp generateAsync/loadAsync)(#1479)

- e2e waits: 移除固定 sleep/忙等,改为条件等待;并升级 workflow action runtime 版本

- server-fixtures: 共享 fixture + mock server
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants